namespace Nico {
    public class ConnectionHandler : GLib.Object {
        private Server server;
        private GLib.Socket connection;
        private string username = "";

        public ConnectionHandler(Server s, GLib.Socket con) {
            this.server = s;
            this.connection = con;
            try {
                new Thread<int>.try("connhandler", this.run);
            } catch (Error e) {
                warning ("Could not start conenctionhandler");
            }
        }

        public bool is_from(GLib.SocketConnectable address) {
            var mine = this.connection.get_remote_address().to_string().split(":")[0];
            var theirs = address.to_string().split(":")[0];
            return mine == theirs;
        }

        private int run() {
            while (!this.connection.is_closed()) {
                try {
                    var valid = true;
                    var msgbuf = new uint8[1024];
                    this.connection.receive(msgbuf);
                    switch (msgbuf[0]) {
                        case Messages.HELO:
                            var msg = (string)msgbuf;
                            var content = msg.substring(1);
                            var helo = (Helo)Json.gobject_from_data(typeof(Helo), content);
                            message("Got connection from user %s", helo.username);
                            this.username = helo.username;
                            this.send_init();
                            break;
                        case Messages.CREATE:
                            var msg = (string)msgbuf;
                            var content = msg.substring(1);
                            var session = (Session)Json.gobject_from_data(typeof(Session), content);
                            message("Got create request from user %s", this.username);
                            this.create_session(session);
                            break;
                        case Messages.ACK:
                            var msg = (string)msgbuf;
                            var content = msg.substring(1);
                            var ack = (Ack)Json.gobject_from_data(typeof(Ack), content);
                            message("Got ack from user %s", this.username);
                            this.server.join_session(ack.session_id, ack.resources, username);
                            this.broadcast_update(ack.session_id);
                            break;
                        case Messages.NACK:
                            var msg = (string)msgbuf;
                            var content = msg.substring(1);
                            var nack = (Nack)Json.gobject_from_data(typeof(Nack), content);
                            message("Got nack from user %s", this.username);
                            this.server.refuse_session(nack.session_id, username);
                            this.broadcast_update(nack.session_id);
                            break;
                        default:
                            valid = false;
                            break;
                    }
                    if (!valid) {
                        break;
                    }
                } catch (Error e) {
                    stdout.printf("Error receiving: "+e.message);
                    break;
                }
            }
            this.connection.close();
            return 0;
        }

        private void send_init() {
            var init = new Init();

            // Accumulate locations
            uint i = 0;
            init.locations = {"vordertuer","hintertuer"};

            // Accumulate sessions
            var sess = this.server.get_sessions().copy();
            var sessions = new Session[sess.length()];
            i = 0;
            foreach (var ses in sess) {
                sessions[i++] = ses;
            }
            init.sessions = sessions   ; 

            size_t len = 0;
            string data = " "+init.serialize();
            if (len > 1023) {
                warning("Could not send init to %s: Message would be too long", this.username);
                return;
            }
            var bytes = data.data;
            bytes[0] = Messages.INIT;
            try {
                this.connection.send(bytes);
            } catch (Error e) {
                this.connection.close();
            }
        }

        private void create_session(Session create) {
            create.users = {this.username};
            this.server.add_session(create);
            this.broadcast_update(create.id);
        }

        private void broadcast_update(int session_id) {
            try {
                var update_thread_context = new UpdateThreadContext(session_id, this.server);
                new Thread<int>.try("update", update_thread_context.run);
            } catch (Error e) {
                warning("Could not start update thread: %s",  e.message);
            }
        }

        public void send_update(Session s) {
            size_t len = 0;
            string data = " "+Json.gobject_to_data(s, out len);
            if (len > 1023) {
                warning("Could not send init to %s: Message would be too long", this.username);
                return;
            }
            var bytes = data.data;
            bytes[0] = Messages.UPDATE;
            try {
                this.connection.send(bytes);
            } catch (Error e) {
                this.connection.close();
            }
        }
    }

    class UpdateThreadContext : GLib.Object {
        private int s;
        private Server server;

        public UpdateThreadContext(int s, Server server) {
            this.s = s;
            this.server = server;
            message ("Triggered update for session id %d", s);
        }

        public int run() {
            this.server.broadcast_update(s);
            return 0;
        }
    }
}
